整理資料的API們:Array、Time Formats、Number Formats、Random
上一篇介紹完怎麼取得不同格式的資料後,接著要來看的是:怎麼把資料整理或篩選出我們想要的內容~
由於大多數的情況下,使用 d3 的 API 時都必須帶入陣列格式的資料,因此 d3 Array 這個分類也提供最多能協助我們整理資料的API ,先來看看 array 分類中包含的子項目:
Statistics 統計數據
用來運算基本的數據,常用的 API 有: d3.min、d3.max、d3.extent、d3.sum
Search 尋找
用來搜尋陣列給指定的DOM元素使用,常用的 API 有:d3.ascending、d3.descending
Transformations 改變結構
用來改變陣列並生成一個新的陣列,常用的 API 有:d3.merge、d3.range
Iterables 迭代
常用的 API 有: d3.every、d3.some、d3.map、d3.filter、d3.sort
Sets 數組
比較多組資料集的交集/差集狀態,並根據使用的 API 返還一個物件
Histograms 直條圖
把離散的資訊變成連續、不重疊的整數,來產生可以繪製直條圖的數據資料
Interning
使用陣列的key或value
D3 Array 包含非常多API,涵蓋幾乎所有陣列相關的資料處理,有興趣的人可以自行上官方文件查找,我這邊只挑幾個最常用到的來介紹:
d3.min
、 d3.max
⇒ 取最小值/最大值這兩個方法跟 JS 的 Math.min、Math.max 基本上是一樣的,都是在一個陣列中找出最小或最大值,但有一個細微的差異是,d3.min/max 會忽略掉 undefined, null 或 NaN 的值。我們直接看到以下範例:
const data = [7, 5, 1, 13, 55, 2, 64, null];
const min = d3.min(data);
const max = d3.max(data);
console.log(min, max) // 1, 64
要特別注意的是,這兩個方法只適合用在比較數字。如果陣列中的內容是字串的話,根據官網解說 (特別感謝邦友 @smallstars10 協助解釋),D3.js 會使用 "natural order" 而非 "numeric order"來排序,也就是說字串會先被 轉換成 UTF-16 code unit (介於 0~65535 的整數),接著再根據這個數值去比大小
const dataString = ['狗', '貓', '羊', '豬']
const min = d3.min(dataString )
const max = d3.max(dataString );
console.log(min, max) // '狗' '貓'
最終出來的結果不一定會符合希望能達到的排序結果。
d3.extent
⇒ 同時返回最大與最小值這是超級方便的一個方法,能同時把資料中的最小值與最大值挑出來,並返還成一個陣列。這個方法通常在設定 scale 那邊會用到,等之後到了 scale 的章節會再講解~
const data = [7, 5, 1, 13, 55, 2, 64, null];
const extent = d3.extent(data); // [1, 64]
d3.sum
⇒ 把資料加總這個方法會把傳入的資料陣列都加總起來,並返還加總的數值;如果資料陣列中沒有可以加總的數值的話 (例如全部都是字串),就會返還0。
// 數字可以加總
const data = [7, 5, 1, 13, 55, 2, 64, null];
const sum = d3.sum(data)
console.log(sum); // 147
// 字串無法加總
const dataString = ['狗', '貓', '羊', '豬']
const sumString = d3.sum(dataString)
console.log(sumString); // 0
d3.every
⇒ 遍歷資料陣列,確認陣列內的值是否全都符合條件每次遇到新的API時,我都會先去查官方文件的資料,確認是否有必須要帶入的參數。我們這次一樣看到官方文件的 API 設定,文件上寫著 # d3.some(iterable, test)
,只要參數沒有用 [ ] 括起來,就代表是必須填入的參數,只要沒填就會報錯
這個方法跟 JS 的 array.every 很相似,一樣需要帶入兩個參數:d3.every ( 資料, 方法 ),帶入的這個方法會遍歷帶入的資料陣列,如果陣列內的每個數都符合設定的條件,就會回傳true;如果其中任何一個數不符合條件,則會回傳false
// 全部的數字都非空值
const data = [7, 5, 1, 13, 55, 2, 64];
const every = d3.every(data, d=>d)
console.log(every); // true
// 全部的數字都大於20
const data = [7, 5, 1, 13, 55, 2, 64];
const every = d3.every(data, d => d > 20)
console.log(every); // false
d3.some
⇒ 遍歷資料陣列,確認陣列內的任一資料是否符合條件跟 d3.every 一樣是帶入兩個參數:d3.some( 資料, 方法 ),但用法剛好相反;帶入的這個方法會遍歷資料陣列,如果陣列內的任一資料符合設定的條件,就會回傳true;如果全部都不符合,才會回傳false
// 其中任意一個數字大於20
const data = [7, 5, 1, 13, 55, 2, 64];
const some = d3.some(data, d => d > 10)
console.log('some',some); // true
d3.filter
⇒ 遍歷資料陣列,返還陣列內所有符合條件的資料這個方法跟 array.filter 很相似,一樣是帶入兩個參數:d3.filter( 資料, 方法 ),在帶入的方法內設定條件,並設定只返還所有符合條件的資料。
const data = [7, 5, 1, 13, 55, 2, 64];
const filter = d3.filter(data, d=>d>10)
console.log('filter',filter); // [13, 55, 64]
d3.sort
⇒ 按照條件排序資料陣列這個方法是用來整理並排序陣列中的資料,它一樣要帶入兩個參數:d3.sort( 資料, 方法 ),比較特別的是,如果參數沒有帶入方法的話,d3.sort就會自動使用 d3.ascending 當作它的參數,並按照 d3.ascending 的方法排序陣列 (由小排到大)
const data = [7, 5, 1, 13, 55, 2, 64, null];
const sort= d3.sort(data)
console.log('sort',sort); // [1, 2, 5, 7, 13, 55, 64, null]
陣列最常用的方法就介紹到這邊~接著我們來看看另外一個類型吧!
除了整理陣列資料之外,有時候我們的圖表會需要帶上一些日期、時間等數據資料,這時候就需要 轉換日期或時間的API
啦~ 我個人認為時間或日期的轉換實在頗複雜,還好 D3 有一些內建好的API 可以使用。
一開始看到 Time Format 的官方文件時,會發現大部分的API 都寫著:alias for _____ on the default local. 這是因為D3 v3 以前的版本是用 locale 這個 API 在處理日期、時間、語言的轉換,而 v4 之後的版本將它重新改名變成 Time Formats 系列,但它們的方法都是一樣的。
這邊一樣介紹幾個比較常用到的API,有其他需求的人可以直接上 Time Formats 官方文件 去找自己想使用的方法。我自己比較常用的API 是:
d3.timeParse
⇒ 將日期等資訊轉換成 D3 看得懂的數值這個方法是 D3 用來處理時間格式的 API。只要是跟時間、日期相關的圖表,都要用這個方法先把資料轉換成D3能讀懂、能夠去計算的數值,之後才能去建構圖表,通常都是用在設定圖表的 scale( ) 或 domain( )。
d3.timeParse( ) 本身是一個方法,我們呼叫它時要帶入特定的參數 (符合要處理的資料格式);之後 d3.timeParse( ) 會回傳另一個方法,我們再接著使用這個回傳的方法去計算要帶入的資料,實際運作如下:
const timeData = '2021-09-07'
const timeParse = d3.timeParse('%Y-%m-%d')
timeParse(timeData) // Tue Sep 07 2021 00:00:00 GMT+0800 (台北標準時間)
要注意的是,這邊帶入的參數格式一定要符合資料的格式哦,如果資料格式是 "2021/09/07"
,參數也要改成 d3.timeParse ('%Y/%m/%d')
,否則你就會得到 null 的結果。
至於可以帶入那些參數呢?我常用的基本上有 (按照常用順序排列):
參數 | 格式 |
---|---|
%Y | 西元年 |
%y | 西元年最末的兩位數 |
%m | 一年的某一個月 ( 01 到 12 ) |
%d | 一月的某一天 (1 到 31) |
%j | 一年的某一天 ( 001 到 366 ) |
%B | 月份 |
%b | 月份的縮寫 |
%A | 星期幾 |
%a | 星期幾的縮寫 |
其他的就比較沒那麼常用,如果有需要或是想看詳細版的人,可以上官方文件 locale.format 去查找~
d3.timeFormat
⇒ 將D3的日期時間數值轉換成我們看得懂的文字d3.timeFormat( ) 的寫法跟 d3.timeParse( ) 是一樣的,只是作用剛好相反。d3.timeFormat( ) 是把 D3能處理的數值,轉成我們看得懂的文字,通常都用在 axis( ) 跟 ticks( ) 畫軸線、標示刻度時使用。
這個 API 通常是在畫軸線、標示刻度時使用,寫法跟 d3.timeParse( ) 稍微不同的地方是 d3.timeFormat( ) 可以自己設定轉換後日期的顯示格式,所以就不用按照原始資料的日期格式來進行參數配置。直接看例子
// Time Formats 轉換成 D3 看得懂的數值
const timeData = '2021-09-07'
const timeParse = d3.timeParse('%Y-%m-%d')
const parsedData = timeParse(timeData)
console.log(parsedData); // 得到D3看得懂的數值
// Time Formate 轉換成人類看得懂的數值
const timeFormat = d3.timeFormat('%Y/%m/%d') // 轉換後想變成用 "/" 分隔
console.log(timeFormat(parsedData)) // 2021/09/07
有關 Time Formats 的方法目前先瞭解這樣就夠了,之後的「軸線與刻度 Axis & ticks 」篇章會有實際的應用~
除了 Time Formats 之外,還有另外一個分類的 Number Formats 專門在處理數字格式的轉換。其實數字並不需要經過特殊處理就能直接使用在d3.scale 的 API 中,但如果希望呈現特定的數字格式,就可以用 D3 Time Formats 提供的 API 快速處理,方便許多~
Time Formats 的 API 包含以下幾種:
我自己偶爾會用到的就是 d3.format
,而且通常也是在處理 Axis 軸線時使用。
d3.format
⇒ 轉換數字格式d3 number format 系列跟 d3 Time Format 很相似,在v3版以前都是歸類在local 下方的,也因此如果你查官方文件的話,會被指引到 local.format 的頁面。
d3.format( ) 一樣是要帶入參數,參數填入欲設定的數字格式,接著呼叫此API後會回傳一個方法,透過這個方法就能將資料轉成我們想要的格式
// Number Formate
const originNumber = 10
const numberFormat = d3.format('b') // 參數 b 是指二進位制
console.log(numberFormat(originNumber)); // 1010
至於哪些參數分別代表什麼樣的格式呢? 這邊列出幾個最常用的,如果想看詳細參數設定,可以參考這邊:
參數 | 格式 | 範例 |
---|---|---|
d | 返回這個數字的字串格式,忽略任何非整數值 | d3.format('d')(12.35) |
g | 指定位數 (參數前需要填入幾位數) | d3.format('2g')(120) |
f | 指定小數點後的位數( 參數前需要先填上幾位數) | d3.format('.2f')(3.14159) |
最後再講另一類偶爾會用到的API: D3 Random Numbers 。D3 Random Numbers 這類的方法主要是快速產生一些亂數以供我們使用。
D3 一樣提供了超級多亂數相關的API,以下介紹比較常用到的:
d3.randomInt
⇒ 產生一個隨機整數我們一樣先來看看官方文件對這個 API 的解釋
文件上說明,d3.randomInt 這個 API 會回傳一個方法,我們可以使用這個方法在指定的範圍生出一個隨機整數。至於怎麼設定亂數的範圍呢?這個 API 可以帶入兩個參數,這兩個參數分別代表著範圍的最小值與最大值,舉例來說,如果我們想要一個介於50跟100之間的隨機整數:
//使用 API 設定範圍
d3.randomInt(50,100)
// 使用方法生成亂數
d3.randomInt(50,100)() //76
這樣就能成功得到一個隨機整數了,是不是非常簡單呢?
好啦!整理資料的 API 就講到這邊~常用到的我都已經搜集在這篇文了,如果有沒提到但你又需要使用的 API,建議直接去看官方文件哦~會省下很多東查查西查查的時間。如果還不太知道這些 API 要用在哪邊也沒關係,等到之後實作的篇章時,就更清楚該如何去使用~今天就先講到這邊啦,我們明天見!
最後附上本章的程式碼與圖表 Github 、 Github Page,今天的頁面一樣要開啟devTool 來看console 內容哦,需要的人請自行取用~
d3.min
與 d3.max
對字串的比較並不是隨機的
字串會先被轉換成 UTF-16 code unit,是介於0~65535的整數
然後才比較大小const dataString = ['狗', '貓', '羊', '豬']
dataString.forEach(d => console.log(d.charCodeAt()))
// 29399, 35987, 32650, 35948
charCodeAt的MDN
原來如此!非常感謝你的解釋~想問一下,我看官網上的文件是說"elements are compared using natural order rather than numeric order.",但並沒有特別註明 natural order 是使用 UTF-8/UTF-16,請問這部分是從哪裡得知的呢? 再次感謝你協助釐清!我再來修正一下我的文章~
我是看Array.prototype.sort()去猜他可能是用Unicode的方式去比較
以下是根據我測試的結果做結論,沒有參考文件
我不確定d3.max
與 d3.min
在比大小用了哪些演算法
可以確定的是string不是只有使用Unicode
還會用到Lexicographic order
也就是從第一個字開始比,一樣的話再比第二個,以下範例就可以看出來const dataString = ['貓', '貓狗貓', '狗', '羊']
dataString.forEach(d => console.log(d.charCodeAt()))
// 35987, 35987, 29399, 32650
const min = d3.min(dataString)
const max = d3.max(dataString)
console.log(min, max) // '狗' '貓狗貓'
同樣都是35987,卻選了'貓狗貓'當max
另一個英文字串的例子const strList = ['Driven', 'Data', 'Documents']
strArr.forEach(d => console.log(d.charCodeAt())) // 都是68
console.log(d3.min(strList)) // 'Data'
但是針對number就不會用到Unicodeconst numberArray = [1002, 30, 4, 21, 101]
const min = d3.min(numberArray)
const max = d3.max(numberArray)
console.log(min, max) // 4, 1002
如果是字串化的數字還是會用Unicode、Lexicographic orderconst stringifyNumberList = ['1032', '30', '4', '21', '101']
const min = d3.min(stringifyNumberList)
const max = d3.max(stringifyNumberList)
console.log(min, max) // 101, 4
不過我覺得實務上很少用string在比大小的,通常都是用number
以上如果有錯誤的部份,請其他大神幫忙修正,感謝
感謝解釋!